#include <iostream>
#include <math.h>
#include <OpenSG/OSGGLUT.h>
#include <OpenSG/OSGConfig.h>
#include <OpenSG/OSGSimpleGeometry.h>
#include <OpenSG/OSGGLUTWindow.h>
#include <OpenSG/OSGSimpleSceneManager.h>
#include <OpenSG/OSGConfig.h>
#include <OpenSG/OSGGLUT.h>
#include <OpenSG/OSGSimpleGeometry.h>
#include <OpenSG/OSGGLUTWindow.h>
#include <OpenSG/OSGBaseFunctions.h>
#include <OpenSG/OSGSpotLight.h>
#include <OpenSG/OSGDirectionalLight.h>
#include <OpenSG/OSGPointLight.h>
#include <OpenSG/OSGPerspectiveCamera.h>
#include <OpenSG/OSGGradientBackground.h>
#include <OpenSG/OSGSolidBackground.h>
#include <OpenSG/OSGDrawAction.h>
#include <OpenSG/OSGRenderAction.h>
#include <OpenSG/OSGImage.h>
#include <OpenSG/OSGSimpleMaterial.h>
#include <OpenSG/OSGTextureChunk.h>
#include <OpenSG/OSGTransform.h>
#include <OpenSG/OSGGroup.h>
#include <OpenSG/OSGTrackball.h>
#include <OpenSG/OSGMatrix.h>
#include <OpenSG/OSGMatrixUtility.h>
#include <OpenSG/OSGQuaternion.h>
#include <OpenSG/OSGVector.h>
#include <OpenSG/OSGSimpleSceneManager.h>
#include <OpenSG/OSGSceneFileHandler.h>
#include <OpenSG/OSGNavigator.h>
#include <OpenSG/OSGShadowViewport.h>
#include <OpenSG/OSGFileSystem.h>

//FMOD includes
#if defined(WIN32) || defined(__WATCOMC__)
    #include <windows.h>
    #include <conio.h>
#else
    #include "includes/wincompat.h"
#endif

#include "includes/fmod.h"
#include "includes/fmod_errors.h"    /* optional */

OSG_USING_NAMESPACE

using std::cout;
using std::endl;

SimpleSceneManager *mgr;
TransformPtr tbox;
TransformPtr dir1_trans;
PointLightPtr _dir1_core;
ShadowViewportPtr svp;
NodePtr rootNode;

FMUSIC_MODULE *mod = NULL;
FSOUND_SAMPLE *bgmusic = 0, *tardis_landing=0;

int brake=0;

int setupGLUT( int *argc, char *argv[] );

int main(int argc, char **argv)
{

    if (FSOUND_GetVersion() < FMOD_VERSION)
    {
        printf("Error : You are using the wrong DLL version!  You should be using FMOD %.02f\n", FMOD_VERSION);
        exit(1);
    }
    
    /*
        INITIALIZE
    */
	FSOUND_SetOutput(FSOUND_OUTPUT_ALSA);
    if (!FSOUND_Init(22000, 64, 0))
    {
        printf("%s\n", FMOD_ErrorString(FSOUND_GetError()));
        exit(1);
    }

  
    /*
        LOAD SONG
    */

bgmusic = FSOUND_Sample_Load(FSOUND_UNMANAGED, "sounds/background.mp3", FSOUND_NORMAL, 0, 0);

 if (!bgmusic)
    {
        printf("Error!\n");
        printf("%s\n", FMOD_ErrorString(FSOUND_GetError()));
        return 1;
    }
tardis_landing = FSOUND_Sample_Load(FSOUND_UNMANAGED, "sounds/tardis_landing.wav", FSOUND_NORMAL, 0, 0);

 if (!bgmusic)
    {
        printf("Error!\n");
        printf("%s\n", FMOD_ErrorString(FSOUND_GetError()));
        return 1;
    }
 FSOUND_SetLoopMode(1,FSOUND_LOOP_NORMAL);
 FSOUND_Sample_SetMode(bgmusic,FSOUND_LOOP_NORMAL);
 FSOUND_PlaySound(1,bgmusic);

    osgInit(argc,argv);

    int winid = setupGLUT(&argc, argv);

    GLUTWindowPtr gwin= GLUTWindow::create();
    gwin->setId(winid);
    gwin->init();


// A simple scene

	Matrix m;
    //Point Light 1
    NodePtr                 dir1 = makeCoredNode<PointLight>(&_dir1_core);
    NodePtr                 dir1_beacon = makeCoredNode<Transform>
        (&dir1_trans);
    beginEditCP(dir1_trans);
    dir1_trans->getMatrix().setTranslate(50.0, 50.0, 0.0);
    endEditCP(dir1_trans);

    beginEditCP(_dir1_core);
    _dir1_core->setAmbient(0.15, 0.15, 0.15, 1);
    _dir1_core->setDiffuse(0.5, 0.5, 0.5, 1);
    _dir1_core->setSpecular(0.0, 0.0, 0.0, 1);
    _dir1_core->setBeacon(dir1_beacon);
    _dir1_core->setOn(true);
    _dir1_core->setShadowIntensity(0.8);
    endEditCP(_dir1_core);

	NodePtr GroundPlane = makePlane(50,50,1,1);
	NodePtr Box = makeBox(2,2,2,1,1,1);

	tbox = Transform::create();
	beginEditCP(tbox);
		m.setIdentity();
		m.setTranslate(0, 0, 0);
		tbox->setMatrix(m);
	endEditCP(tbox);
		
	NodePtr BoxT = Node::create();
	beginEditCP(BoxT);
		BoxT->setCore(tbox);
		BoxT->addChild(Box);
	endEditCP(BoxT);

	TransformPtr tgp = Transform::create();
	beginEditCP(tgp);
		m.setIdentity();
		m.setTranslate(0, -1, 0);
		m.setRotate(Quaternion(Vec3f(1,0,0), -M_PI/2));
		tgp->setMatrix(m);
	endEditCP(tgp);
		
	NodePtr GroundPlaneT = Node::create();
	beginEditCP(GroundPlaneT);
		GroundPlaneT->setCore(tgp);
		GroundPlaneT->addChild(GroundPlane);
	endEditCP(GroundPlaneT);

	NodePtr scene = Node::create();
	beginEditCP(scene);
		scene->setCore(Group::create());
		scene->addChild(GroundPlaneT);
		scene->addChild(BoxT);
	endEditCP(scene);

    beginEditCP(dir1);
    dir1->addChild(scene);
    endEditCP(dir1);

    rootNode = makeCoredNode<Group>();

    beginEditCP(rootNode);
    rootNode->addChild(dir1);
    rootNode->addChild(dir1_beacon);
    endEditCP(rootNode);


    svp = ShadowViewport::create();
    GradientBackgroundPtr   gbg = GradientBackground::create();
    SolidBackgroundPtr      sbg = SolidBackground::create();

    beginEditCP(gbg);
    gbg->addLine(Color3f(0.7, 0.7, 0.8), 0);
    gbg->addLine(Color3f(0.0, 0.1, 0.3), 1);
    endEditCP(gbg);

    // Shadow viewport
    beginEditCP(svp);
    svp->setBackground(sbg);
    svp->setRoot(rootNode);
    svp->setSize(0, 0, 1, 1);
    //svp->setOffFactor(4.0);
    //svp->setOffBias(8.0);
    //used to set global shadow intensity, ignores shadow intensity from light sources if != 0.0
    //svp->setGlobalShadowIntensity(0.8);
    svp->setMapSize(1024);
    svp->setShadowMode(ShadowViewport::PCF_SHADOW_MAP);
    //ShadowSmoothness used for PCF_SHADOW_MAP and VARIANCE_SHADOW_MAP, defines Filter Width. Range can be 0.0 ... 1.0.
    //ShadowSmoothness also used to define the light size for PCSS_SHADOW_MAP
    svp->setShadowSmoothness(0.5);
    // add light sources here
    svp->getLightNodes().push_back(dir1);
    //svp->setAutoSearchForLights(true);
    endEditCP(svp);



    beginEditCP(gwin);//Window
    gwin->setId(winid);
    gwin->addPort(svp);
    gwin->init();
    endEditCP(gwin);

    mgr = new SimpleSceneManager;
    mgr->setWindow(gwin );
    mgr->setRoot  (rootNode);
    mgr->setNavigationMode(Navigator::FLY);
    mgr->getNavigator()->setMotionFactor(0.002);

    beginEditCP(svp);
        svp->setCamera(mgr->getCamera());
    endEditCP(svp);

	mgr->getNavigator()->getWalkNavigator()->setGroundDistance(1);
	mgr->getNavigator()->getWalkNavigator()->setMinWallDistance(1);
	mgr->getNavigator()->getWalkNavigator()->setPersonDimensions(2,2,2);
	mgr->getNavigator()->getWalkNavigator()->setUp(Vec3f(0,1,0));
	mgr->getNavigator()->getWalkNavigator()->setWorld(scene);
	mgr->getNavigator()->getWalkNavigator()->setGround(scene);

    mgr->setStatistics(true);

    mgr->showAll();

    glutMainLoop();

    return 0;
}


void display(void)
{
	brake++;
	if (brake==10){
	brake=0;
	int morphx,morphy,morphz;

	Matrix m;

	morphx = 1 + ( rand() % 5 );
	morphy = 1 + ( rand() % 5 );
	morphz = 1 + ( rand() % 5 );

	morphx-=3;
	morphy-=3;
	morphz-=3;

//cout << morphx << endl;

	m.setIdentity();
	m.setTranslate(0+morphx,0+morphy,0+morphz);
//	m.setTranslate(morphx,morphy,morphz);
//	m.setScale(morphx);

/*	beginEditCP(tbox);
		tbox->setMatrix(m);
	endEditCP(tbox);*/
	}


    static Real64   t0 = OSG::getSystemTime();

    Real64          t = OSG::getSystemTime() - t0;

    Real32          rot0 = t * 0.25;
    if(rot0 > 360.0)
        rot0 -= 360.0;

    Real32          rota = t * 0.5;
    if(rota > 360.0)
        rota -= 360.0;

    Real32          rotb = t * 1.0;
    if(rotb > 360.0)
        rotb -= 360.0;

    Real32          rotc = t * 1.5;
    if(rotc > 360.0)
        rotc -= 360.0;

    Real32          rotd = t * 2.0;
    if(rotd > 360.0)
        rotd -= 360.0;
 
   beginEditCP(dir1_trans);
    {
        dir1_trans->getMatrix().setTranslate(50.0 * sin(rotb), 50.0, 50.0 * cos(rotb));
    }
    endEditCP(dir1_trans);








   mgr->idle();
   mgr->redraw();
}

// react to size changes
void reshape(int w, int h)
{
    mgr->resize(w, h);
    glutPostRedisplay();
}

// react to mouse button presses
void mouse(int button, int state, int x, int y)
{
    if (state)
        mgr->mouseButtonRelease(button, x, y);
    else
        mgr->mouseButtonPress(button, x, y);
}

// react to mouse motions with pressed buttons
void motion(int x, int y)
{
    mgr->mouseMove(x, y);
}

// react to keys
void keyboard(unsigned char k, int x, int y)
{
    switch(k)
    {
        case 27:        
        {
            OSG::osgExit();
            exit(0);
        }
        break;
	case '1':   mgr->setNavigationMode(Navigator::FLY);
	break;
	case 't':    FSOUND_PlaySound(2,tardis_landing);
	break;

	case '2':   mgr->setNavigationMode(Navigator::TRACKBALL);
	break;

	case '3':
	mgr->setNavigationMode(Navigator::WALK);
	mgr->getNavigator()->getWalkNavigator()->setUp(Vec3f(0,1,0));
	break;
    }
}

// setup the GLUT library which handles the windows for us
int setupGLUT(int *argc, char *argv[])
{
    glutInit(argc, argv);
    glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE);
    glutInitWindowSize(800,600);

    int winid = glutCreateWindow("Wookies Sandbox");

    glutReshapeFunc(reshape);
    glutDisplayFunc(display);
    glutMouseFunc(mouse);
    glutMotionFunc(motion);
    glutKeyboardFunc(keyboard);
    glutIdleFunc(display);

    return winid;
}
